/**
 * Copyright (C) 2013-2014 the original author or authors.
 */
package com.foreveross.bsl.mongodb.repository.handler;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.springframework.dao.DataAccessException;
import org.springframework.data.authentication.UserCredentials;
import org.springframework.data.mongodb.core.MongoDbUtils;
import org.springframework.util.Assert;

import com.foreveross.bsl.mongodb.repository.router.RouterMongodbFactory;
import com.foreveross.bsl.mongodb.repository.source.MongodbSources;
import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.mongodb.Mongo;
import com.mongodb.WriteConcern;

/**
 * @description
 *
 * @author rening
 * @version v1.0
 *
 * @2013-7-17
 *
 */
public class MultRouterScheme implements RouterScheme {
	
	private UserCredentials credentials;
	private WriteConcern writeConcern;
	private DB db;

	private String routerDatabase = "router_database"; //router database
	private String routerConnection = "router_connection"; //router connection 
	private String globalDatabasePackage;
	
	private RouterMongodbFactory mongodbFactory;

	public MultRouterScheme(UserCredentials credentials, RouterMongodbFactory mongoDbFactory){
		this.credentials = credentials;
		this.mongodbFactory = mongoDbFactory;
	}
	
	/*
	 * (non-Javadoc)
	 * @see org.springframework.data.mongodb.MongoDbFactory#getDb(java.lang.String)
	 */
	@Override
	public DB getDb(String registerNo, Class clazz) throws DataAccessException {
		String connName = "";
		String dataBaseName = "";
		Package p = null;
		if(clazz !=null){
			p = clazz.getPackage();
			connName = getConnName(p.getName());
		} 
		if(matcheGlobal(p)){
			dataBaseName = mongodbFactory.getDefaultDatabaseName();
		} else {
			DBObject obj = getDatabaseDBObject(registerNo);
			if(obj != null){
				dataBaseName = (String)obj.get("database_name");
			} else {
				dataBaseName = mongodbFactory.getDefaultDatabaseName();
			}
		}
		
		Mongo curMongo;
		if(!"".equals(connName)){
			curMongo = MongodbSources.getMongo(connName);
		} else {
			curMongo = mongodbFactory.getMongo();
		}
		DB db = MongoDbUtils.getDB(curMongo, dataBaseName, credentials);
		if (writeConcern != null) {
			db.setWriteConcern(writeConcern);
		}

		return db;
	}

	/* (non-Javadoc)
	 * @see com.foreveross.common.repository.mongodb.handler.RouterScheme#getDb(java.lang.String, java.lang.Class, java.lang.String)
	 */
	@Override
	public DB getDb(String registerNo, Class clazz, String dbName) throws DataAccessException {
		String connName = "";
		if(clazz !=null){
			Package p = clazz.getPackage();
			connName = getConnName(p.getName());
		} 
		Mongo curMongo;
		if(!"".equals(connName)){
			curMongo = MongodbSources.getMongo(connName);
		} else {
			curMongo = mongodbFactory.getMongo();
		}
		DB db = MongoDbUtils.getDB(curMongo, dbName, credentials);
		if (writeConcern != null) {
			db.setWriteConcern(writeConcern);
		}

		return db;
	}
	
	/**
	 * routerDatabase collection keys:id,registerNo,databaseName
	 * @param key
	 * @param clazz
	 * @return
	 */
	private <T> DBObject getDatabaseDBObject(String key) {
		db = MongoDbUtils.getDB(mongodbFactory.getMongo(), mongodbFactory.getDefaultDatabaseName(), credentials);
		
		if(db.collectionExists(routerDatabase)){
			DBCollection coll = db.getCollection(routerDatabase);
			BasicDBObject obj = new BasicDBObject("register_no",key);
			return coll.findOne(obj);
		} else {
			throw new RuntimeException("routerDatabase is not exist!");
		}
	}
	
	/**
	 * router collection keys: id,packageName,connName
	 * @param packageName
	 * @return
	 */
	private String getConnName(String packageName){
		Assert.hasText(packageName, "packageName must not be empty");

		db = MongoDbUtils.getDB(mongodbFactory.getMongo(), mongodbFactory.getDefaultDatabaseName(), credentials);
		
		if(db.collectionExists(routerConnection)){
			DBCollection coll = db.getCollection(routerConnection);
			DBCursor cur = coll.find();
			
			DBObject obj;
			Pattern pattern;
			Matcher matcher;
			String connName = "";
			while (cur.hasNext()) {
				obj = cur.next();
				if(obj.get("package_name") != null){
					pattern = Pattern.compile((String)obj.get("package_name"));
					matcher = pattern.matcher(packageName);
					if(matcher.matches()){
						connName = (String)obj.get("conn_name");
						break;
					}	
				}
			}
			if("".equals(connName)){
				throw new RuntimeException("can not find the connName by matchering the packageName.");
			}
			return connName;
		} else {
			throw new RuntimeException("routerConnection is not exist!");
		}
	}

	/**
	 * @return the writeConcern
	 */
	public WriteConcern getWriteConcern() {
		return writeConcern;
	}

	/**
	 * @param writeConcern the writeConcern to set
	 */
	public void setWriteConcern(WriteConcern writeConcern) {
		this.writeConcern = writeConcern;
	}
	
	private boolean matcheGlobal(Package p){
		Assert.notNull(p, "Package must be not null");
		if(globalDatabasePackage != null){
			String[] packages = globalDatabasePackage.split(",");
			for (int i = 0; i < packages.length; i++) {
				String packageName = p.getName();
				Pattern pattern = Pattern.compile(packages[i]);
				Matcher matcher = pattern.matcher(packageName);
				if(matcher.matches()){
					return true;
				} 
			}
		}
		return false;
	}

	/**
	 * @return the globalDatabasePackage
	 */
	public String getGlobalDatabasePackage() {
		return globalDatabasePackage;
	}

	/**
	 * @param globalDatabasePackage the globalDatabasePackage to set
	 */
	public void setGlobalDatabasePackage(String globalDatabasePackage) {
		this.globalDatabasePackage = globalDatabasePackage;
	}
	
}
